﻿using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.Web.Administration;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System;
using System.Text.RegularExpressions;
using System.Security.AccessControl;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Diagnostics;

namespace AzureToolkit.MultiTenant
{
    public class WebDeployEngine
    {

        string localPath;
        CloudBlobContainer container;
        List<WebAppInfo> entries;
        HashSet<string> mappings;

        public void Sync()
        {
            HashSet<string> seen = new HashSet<string>();

            // Download all new zipped sites to our local folder
            foreach (var blob in container.ListBlobs(new BlobRequestOptions { BlobListingDetails = BlobListingDetails.Metadata }).OfType<CloudBlob>())
            {
                if (blob.Uri.ToString().EndsWith(".zip"))
                {
                    var hostHeader = blob.Uri.ToString().Substring(container.Uri.ToString().Length + 1).Replace(".zip", string.Empty);
                    var lastModified = blob.Properties.LastModifiedUtc;

                    seen.Add(hostHeader);

                    var entry = entries.SingleOrDefault(e => e.HostHeader == hostHeader);


                    if (entry == null || entry.LastUpdate < lastModified)
                    {
                        // Unzip the site to the host folder
                        var sitePath = Path.Combine(localPath, hostHeader, DateTime.UtcNow.Ticks.ToString());
                        using (var stream = new MemoryStream())
                        {
                            blob.DownloadToStream(stream);

                            var extractor = new SevenZip.SevenZipExtractor(stream);
                            extractor.ExtractArchive(sitePath);
                        }
                        if (entry == null)
                        {
                            entry = new WebAppInfo();
                            entries.Add(entry);
                        }
                        entry.HostHeader = hostHeader;
                        entry.CurrentPath = sitePath;
                        entry.LastUpdate = new FileInfo(sitePath).LastWriteTimeUtc;
                    }
                }
            }

            //foreach (var path in entries.Keys.Where(k => !seen.Contains(k)).ToArray())
            //{
            //    Directory.Delete(Path.Combine(localPath, path), true);
            //    entries.Remove(path);
            //}

            UpdateSites();
        }

        public void SyncForever(TimeSpan interval)
        {
            while (true)
            {
                try
                {
                    Sync();
                }
                catch (Exception e) // log all exceptions to blobs
                {
                    var errors = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("DataConnectionString")).CreateCloudBlobClient().GetContainerReference("errors");
                    // errors.CreateIfNotExist();
                    var error = errors.GetBlobReference((DateTime.MaxValue - DateTime.UtcNow).Ticks.ToString("d19") + ".txt");
                    error.Properties.ContentType = "text/plain";
                    error.UploadText(e.ToString());
                }
                Thread.Sleep(interval);
            }
        }

        private void UpdateSites()
        {
            var sm = new ServerManager();

            // Delete sites we dont have any more
            foreach (var site in sm.Sites.Where(s => s.Name.StartsWith(RoleEnvironment.CurrentRoleInstance.Id)).ToArray())
            {
                var name = site.Name.Substring(RoleEnvironment.CurrentRoleInstance.Id.Length + 1).ToLower();
                var entry = entries.SingleOrDefault(e => e.HostHeader == name);
                if (entry == null && name != "web") // never delete "Web," which is the website for this web role
                {
                    var appPoolName = site.Applications[0].ApplicationPoolName;
                    var appPool = sm.ApplicationPools.SingleOrDefault(ap => ap.Name == appPoolName);
                    sm.ApplicationPools.Remove(appPool);
                    sm.Sites.Remove(site);
                }
            }

            // Add new sites
            foreach (var site in entries)
            {
                var siteName = RoleEnvironment.CurrentRoleInstance.Id + "_" + site.HostHeader;
                var sitePath = Path.Combine(site.CurrentPath, "Site");

                if (sm.Sites.Count(s => s.Name == siteName) == 0)
                {
                    // If the site doesnt exist add it
                    // default binding is just the site name (example.org)
                    var newSite = sm.Sites.Add(siteName, "http",
                        RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["HttpIn"].IPEndpoint.ToString() + ":" + site.HostHeader, sitePath);

                    // Bind the SSL if a matching certificate exists
                    X509Certificate cert = GetSiteCertificate(site.HostHeader);
                    if (cert != null)
                    {
                        newSite.Bindings.Add(RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["HttpsIn"].IPEndpoint.ToString() + ":" + site.HostHeader, cert.GetCertHash(), StoreName.My.ToString());
                    }

                    newSite.Applications.First().ApplicationPoolName = GetAppPool(sm, newSite.Name).Name;
                }
                else
                {
                    // Update the site folder if the app already exists
                    var currentSite = sm.Sites.SingleOrDefault(s => s.Name == siteName);
                    currentSite.Applications["/"].Path = sitePath;
                }
            }

            sm.CommitChanges();

            //// Delete folders we dont use any more
            //foreach (var directory in Directory.EnumerateDirectories(localPath))
            //{
            //    var host = directory.Replace(localPath, String.Empty).Replace("\\", string.Empty);
            //    if (entries.Count(e => e.HostHeader == host) == 0)
            //    {
            //        Directory.Delete(directory);
            //    }
            //}
        }

        private X509Certificate2 GetSiteCertificate(string siteName)
        {
            // third binding is SSL (if applicable)
            X509Certificate2 cert = null;
            try
            {
                var certPath = Path.Combine(localPath, siteName, "Certificate.pfx");
                var passPath = Path.Combine(localPath, siteName, "Certificate.pass");
                if (File.Exists(certPath) && File.Exists(passPath))
                {
                    var certBytes = File.ReadAllBytes(certPath);
                    var password = File.ReadAllText(passPath);
                    cert = new X509Certificate2(certBytes, password);

                    var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                    if (!store.Certificates.Contains(cert))
                    {
                        try
                        {
                            store.Open(OpenFlags.ReadWrite);
                            store.Add(cert);
                        }
                        finally
                        {
                            store.Close();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message);
            }

            return cert;
        }

        private ApplicationPool GetAppPool(ServerManager serverManager, string siteName)
        {
            var appPool = serverManager.ApplicationPools.SingleOrDefault(p => p.Name == siteName);
            if (appPool == null)
            {
                appPool = serverManager.ApplicationPools.Add(siteName);
                appPool.ManagedRuntimeVersion = "v4.0";
            }
            return appPool;
        }

        public void Run()
        {
            SyncForever(TimeSpan.FromSeconds(30));
        }

        public void Start()
        {
            SevenZip.SevenZipExtractor.SetLibraryPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "7zip\\7z.dll"));

            localPath = RoleEnvironment.GetLocalResource("Sites").RootPath.TrimEnd('\\');

            var sec = Directory.GetAccessControl(localPath);
            sec.AddAccessRule(new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
            Directory.SetAccessControl(localPath, sec);

            container = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("DataConnectionString")).CreateCloudBlobClient().GetContainerReference(RoleEnvironment.GetConfigurationSettingValue("SitesContainerName"));
            entries = new List<WebAppInfo>();
            mappings = new HashSet<string>();

            Sync();
        }

    }
}